package br.edu.ifrn.potigol.lang;

import java.util.HashMap;

/**
 * Classe no runtime.
 * Classes armazenam métodos e constantes. Cada objeto no tempo de execução tem uma classe.
 */
public class PotigolClass extends PotigolObject {
  private String name;
  private PotigolClass superClass;
  private HashMap<String, Method> methods;
  HashMap<String, PotigolObject> constants;
  
  /**
   * Cria uma classe herdada de uma superclasse.
   */
  public PotigolClass(String name, PotigolClass superClass) {
    super("Class");
    this.name = name;
    this.superClass = superClass;
    methods = new HashMap<String, Method>();
    constants = new HashMap<String, PotigolObject>();
  }
  
  /**
   * Cria uma classe herdada de um objeto
   */
  public PotigolClass(String name) {
    this(name, PotigolRuntime.getObjectClass());
  }
  
  public String getName() {
    return name;
  }
  
  public PotigolClass getSuperClass() {
    return superClass;
  }
  
  public void setConstant(String name, PotigolObject value) {
    constants.put(name, value);
  }

  public PotigolObject getConstant(String name) {
    if (constants.containsKey(name)) return constants.get(name);
    if (superClass != null) return superClass.getConstant(name);
    return PotigolRuntime.getNil();
  }
  
  public boolean hasConstant(String name) {
    if (constants.containsKey(name)) return true;
    if (superClass != null) return superClass.hasConstant(name);
    return false;
  }
  
  public Method lookup(String name) throws MethodNotFound {
    if (methods.containsKey(name)) return methods.get(name);
    if (superClass != null) return superClass.lookup(name);
    throw new MethodNotFound(name);
  }

  public boolean hasMethod(String name) {
    if (methods.containsKey(name)) return true;
    if (superClass != null) return superClass.hasMethod(name);
    return false;
  }
  
  public void addMethod(String name, Method method) {
    methods.put(name, method);
  }
  
  /**
   * Cria uma nova instância de uma classe
   */
  public PotigolObject newInstance() {
    return new PotigolObject(this);
  }
  
  /**
   * Cria uma nova instância de uma classe armazenando o valor dentro de um ValueObject.
   */
  public PotigolObject newInstance(Object value) {
    return new ValueObject(this, value);
  }
  
  /**
   * Cria uma nova subclasse desta classe.
   */
  public PotigolClass newSubclass(String name) {
    PotigolClass klass = new PotigolClass(name, this);
    PotigolRuntime.getObjectClass().setConstant(name, klass);
    return klass;
  }
  
  /**
   * Cria ou caso já exista, retorna uma subclasse.
   */
  public PotigolClass subclass(String name) {
    PotigolClass objectClass = PotigolRuntime.getObjectClass();
    if (objectClass.hasConstant(name)) return (PotigolClass) objectClass.getConstant(name);
    return newSubclass(name);
  }
  
  /**
   * Retorna verdadeiro se klass é uma subclasse desta classe.
   */
  public boolean isSubclass(PotigolClass klass) {
    if (klass == this) return true;
    if (klass.getSuperClass() == null) return false;
    if (klass.getSuperClass() == this) return true;
    return isSubclass(klass.getSuperClass());
  }
  
  @Override
  public boolean equals(Object other) {
    if (other == this) return true;
    if ( !(other instanceof PotigolClass) ) return false;
    return name == ((PotigolClass)other).getName();
  }
}
